/**
 * Copyright (C) 2009+ Spark Zheng
 *
 * @file        src/proto_tcp.c 
 * @date        2010-04-05
 * @brief	general_network
 *		tcp implememtation of generate-operations
 * @see         dserver
 * @history
 */

#include <dlib/mempool.h>

#include "dserver_log.h"
#include "dserver_errno.h"
#include "utils.h"
#include "prot_types.h"
#include "connection.h"

/** tcp server callbacks */
static int tcp_server_init(const char *domain, in_port_t port, uint16_t timeo);
static int tcp_server_exit(int sockfd);
static void tcp_server_conn_handler(int fd, short flag, void *arg);
static int tcp_server_conn_exit(void *arg);
static void tcp_server_data_maker(int fd, short flag, void *arg);
static void tcp_server_data_handler(int fd, short flag, void *arg);
static int tcp_server_data_exit(int sockfd);

prot_ops_t tcp_ops = {
	.p_server_init = tcp_server_init,
	.p_server_exit = tcp_server_exit,
	.p_server_conn_handler = tcp_server_conn_handler,
	.p_server_conn_exit = tcp_server_conn_exit,
	.p_server_data_maker = tcp_server_data_maker,
	.p_server_data_handler = tcp_server_data_handler,
	.p_server_data_exit = tcp_server_data_exit,
};



/** tcp server functions */
static int tcp_server_init(const char *domain, in_port_t port, uint16_t timeo)
{
	int sock = dserver_server_new(domain, port, PROTOCOL_TCP);
	if (sock > 0 && timeo > 0) {
		(void)dserver_set_rcv_timeout(sock, timeo, 0);
		(void)dserver_set_snd_timeout(sock, timeo, 0);
	}
	return sock;
}

static int tcp_server_exit(int sockfd)
{
	return close(sockfd);
}

static void tcp_server_conn_handler(int fd, short flag, void *arg)
{
	int cfd = -1;
	struct sockaddr_in csa;
	socklen_t csa_len = sizeof(csa);
	struct timeval tv;
	dserver_conn_t *conn = NULL;
	prot_node_t *prot = (prot_node_t *)arg;

	UNUSED_PARAM(flag);
	UNUSED_PARAM(fd);

	while (1) {
		cfd = accept(prot->p_sfd, (struct sockaddr *)&csa, &csa_len); 
		if(cfd < 0) {
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
			if (errno != EAGAIN && errno != EWOULDBLOCK) {
#else
			if (errno != EAGAIN) {
#endif
				if (errno == EINTR) {
#ifdef DSERVER_NETWORK_NEED_DLOG
					DLOG_DEBUG(LOG_MAIN, "accept EINTR, continue.");
#endif
					continue;
				} else if (errno == ECONNABORTED) {
#ifdef DSERVER_NETWORK_NEED_DLOG
					DLOG_ERR(LOG_MAIN, "accept ECONNABORTED: %s.",
							strerror(errno));
#endif
					break;
				} else {
#ifdef DSERVER_NETWORK_NEED_DLOG
					DLOG_ERR(LOG_MAIN, "accept other error: %s.",
							strerror(errno));
#endif
					break;
				}
			} else {
#ifdef DSERVER_NETWORK_NEED_DLOG
				DLOG_DEBUG(LOG_MAIN, "accept EAGAIN: %s.", strerror(errno));
#endif
				break;
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
			}
#else
			}
#endif
		} /* cfd < 0 */

		/** set nonblock for client-fd */
		if (dserver_set_nonblock(cfd) < 0) {
#ifdef DSERVER_NETWORK_NEED_DLOG
			DLOG_ERR(LOG_MAIN, "set_nonblock fd[%d] error.", cfd);
#endif
			close(cfd);
			continue;
		}

		/** fetch a connection structure */
		conn = dserver_conn_fetch(prot->p_base.p_tcp_main_cpool);
		if (conn == NULL) {
#ifdef DSERVER_NETWORK_NEED_DLOG
			DLOG_ERR(LOG_MAIN, "prot[%s] dserver fetch connection error.",
					prot->p_name);
#endif
			close(cfd);
			continue;
		}
		conn->c_cfd = cfd;
                conn->c_prot_node = prot;
                conn->c_csa_len = csa_len;	
                memcpy(&conn->c_csa, &csa, csa_len);

		/** session init */
		if (prot->s_ops.p_session_init != NULL) {
			if ((*prot->s_ops.p_session_init)((void *)conn) < 0) {
#ifdef DSERVER_NETWORK_NEED_DLOG
				DLOG_ERR(LOG_MAIN, "protocol[%s] session init error.",
						prot->p_name);
#endif
				close(cfd);
				(void)dserver_conn_restore(prot->p_base.p_tcp_main_cpool, conn);
				continue;
			}
		}/* p_session_init */

		/** add cfd-event to eventpool */
		tv.tv_sec = prot->p_settings.p_timeout;
		tv.tv_usec = 0;
		event_set(&conn->c_event, cfd, EV_READ,
				prot->p_ops.p_server_data_handler, (void *)conn);
		(void)event_base_set(prot->p_main_event, &conn->c_event);
		if (event_add(&conn->c_event, &tv) < 0) {
#ifdef DSERVER_NETWORK_NEED_DLOG
			DLOG_ERR(LOG_MAIN, "protocol[%s] add event error.", prot->p_name);
#endif
			(void)(*prot->p_ops.p_server_conn_exit)(conn);
			continue;
		}
	}/* while true */

	return;
}

static int tcp_server_conn_exit(void *arg)
{
	dserver_conn_t *conn = (dserver_conn_t *)arg;
	prot_node_t *prot = conn->c_prot_node;

	if (prot->s_ops.p_session_exit != NULL) {
		(*prot->s_ops.p_session_exit)((void *)conn);
	}

	conn->c_prot_node = NULL;
	if (conn->c_cfd > 0) {
		close(conn->c_cfd);
		conn->c_cfd = -1;
	}
	conn->c_rsize = 0;
	conn->c_wsize = 0;

	(void)dserver_conn_restore(prot->p_base.p_tcp_main_cpool, conn);

	return 0;
}

static void tcp_server_data_maker(int fd, short flag, void *arg)
{
	ssize_t size;
	struct timeval tv;
	dserver_conn_t *conn = (dserver_conn_t *)arg;
	prot_node_t *prot = conn->c_prot_node;

	UNUSED_PARAM(fd);

	if (flag != EV_WRITE) {
#ifdef DSERVER_NETWORK_NEED_DLOG
		DLOG_WARNING(LOG_MAIN, "prot[%s:%d] write event timeout or error.",
				prot->p_name, conn->c_cfd);
#endif
		return; /* write operation doesn't cause connection exit */
	}

	size = write(conn->c_cfd, conn->c_wbuf, conn->c_wsize);
	if (size < 0) {
		switch (errno) {
			case EINTR:
				size = 0;
				break;
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
			case EWOULDBLOCK:
#endif
			case EAGAIN:
#ifdef DSERVER_NETWORK_NEED_DLOG
				DLOG_DEBUG(LOG_MAIN, "prot[%s:%d] tcp write blocked: %s",
						prot->p_name, conn->c_cfd, strerror(errno));
#endif
				size = 0;
				break;
			default:
#ifdef DSERVER_NETWORK_NEED_DLOG
				DLOG_ERR(LOG_MAIN, "prot[%s:%d] tcp write data error: %s.",
						prot->p_name, conn->c_cfd, strerror(errno));
#endif
				return;
		} /* switch */
	/* } else if (size == 0) {
#ifdef DSERVER_NETWORK_NEED_DLOG
		DLOG_DEBUG(LOG_MAIN, "prot[%s:%d] tcp write[0], peer read-closed.",
				prot->p_name, conn->c_cfd);
#endif
		return; */
	} else {
#ifdef DSERVER_NETWORK_NEED_DLOG
		DLOG_DEBUG(LOG_MAIN, "prot[%s:%d] tcp write data[%d].",
				prot->p_name, conn->c_cfd, size);
#endif
	}

	/** need write more */
	if ((uint32_t)size < conn->c_wsize) {
		if (size > 0) {
			conn->c_wsize -= size;
			memmove(conn->c_wbuf, conn->c_wbuf + size, conn->c_wsize);
		} /* size to be zero */

		tv.tv_sec = prot->p_settings.p_timeout;
		tv.tv_usec = 0;
		event_set(&conn->c_event, conn->c_cfd, EV_WRITE,
				prot->p_ops.p_server_data_maker, (void *)conn);
		(void)event_base_set(prot->p_main_event, &conn->c_event);
		if (event_add(&conn->c_event, &tv) < 0) {
#ifdef DSERVER_NETWORK_NEED_DLOG
			DLOG_ERR(LOG_MAIN, "prot[%s:%d] rewrite add event error.",
					prot->p_name, conn->c_cfd);
#endif
			return;
		}
	} /* write more */

	return;
}

static void tcp_server_data_handler(int fd, short flag, void *arg)
{
	ssize_t size;
	prot_ret_t ret = PROTOCOL_CLOSE;
	struct timeval tv;
	dserver_conn_t *conn = (dserver_conn_t *)arg;
	prot_node_t *prot = conn->c_prot_node;

	UNUSED_PARAM(fd);

	if (flag != EV_READ) { /* timeout when read */
#ifdef DSERVER_NETWORK_NEED_DLOG
		DLOG_ERR(LOG_MAIN, "prot[%s:%d] read event timeout or error.",
				prot->p_name, conn->c_cfd);
#endif
		(void)(*prot->p_ops.p_server_conn_exit)(conn);
		return;
	}

	while (1) {
		size = read(conn->c_cfd, conn->c_rbuf, DSERVER_RX_BUFLEN - 1);
		if (size < 0) {
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
			if (errno != EAGAIN && errno != EWOULDBLOCK) {
#else
			if (errno != EAGAIN) {
#endif
#ifdef DSERVER_NETWORK_NEED_DLOG
				if (errno == ECONNRESET) {
					DLOG_ERR(LOG_MAIN, "prot[%s:%d] tcp read RST: %s",
							prot->p_name, conn->c_cfd, strerror(errno));
					/* conn->c_cfd = -1; */
				} else {
					DLOG_ERR(LOG_MAIN, "prot[%s:%d] tcp read data error: %s",
							prot->p_name, conn->c_cfd, strerror(errno));
				}
#endif
				(void)(*prot->p_ops.p_server_conn_exit)(conn);
				return; /* read error return */
			} else {
#ifdef DSERVER_NETWORK_NEED_DLOG
				DLOG_DEBUG(LOG_MAIN, "prot[%s:%d] tcp read blocked: %s",
						prot->p_name, conn->c_cfd, strerror(errno));
#endif
				break; /* no data for reading readd event */
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
			} /* end if block */
#else
			}
#endif
		} else if (size == 0) {
#ifdef DSERVER_NETWORK_NEED_DLOG
			DLOG_DEBUG(LOG_MAIN, "prot[%s:%d] tcp read FIN.",
					prot->p_name, conn->c_cfd);
#endif
			(void)(*prot->p_ops.p_server_conn_exit)(conn);
			return; /* received FIN and return, close passively */ 
		} else {
#ifdef DSERVER_NETWORK_NEED_DLOG
			DLOG_DEBUG(LOG_MAIN, "prot[%s:%d] tcp read data[%d].",
					prot->p_name, conn->c_cfd, size);
#endif
			conn->c_rsize = size;
			conn->c_rbuf[conn->c_rsize] = 0;

			/** do protocol parser, Must check the reture value */
			if (prot->s_ops.p_session_worker != NULL) {
				ret = (*prot->s_ops.p_session_worker)((void *)conn);
			} else {
				ret = PROTOCOL_CLOSE; /* default close connection */
			}

			/* check return value for persistence connection */
			if (ret == PROTOCOL_CLOSE) {
#ifdef DSERVER_NETWORK_NEED_DLOG
				DLOG_DEBUG(LOG_MAIN, "TCP prot[%s:%d] return close.",
					prot->p_name, conn->c_cfd);
#endif
				(void)(*prot->p_ops.p_server_conn_exit)(conn);
				return;
			} else if (ret == PROTOCOL_CONTINUE) {
#ifdef DSERVER_NETWORK_NEED_DLOG
				DLOG_DEBUG(LOG_MAIN, "TCP prot[%s:%d] return continue.",
					prot->p_name, conn->c_cfd);
#endif
				continue;
			} else if (ret == PROTOCOL_IGNORE) { /* may lost the connection forever */
				/* need setup a event/thread for connection exit */
#ifdef DSERVER_NETWORK_NEED_DLOG
				DLOG_DEBUG(LOG_MAIN, "TCP prot[%s:%d] return ignore.",
					prot->p_name, conn->c_cfd);
#endif
				return;
			} else if (ret == PROTOCOL_RESET) { /* may cause no time_wait */
#ifdef DSERVER_NETWORK_NEED_DLOG
				DLOG_DEBUG(LOG_MAIN, "TCP prot[%s:%d] return reset.",
					prot->p_name, conn->c_cfd);
#endif
				if (prot->s_ops.p_session_exit != NULL) {
					(*prot->s_ops.p_session_exit)((void *)conn);
				}
				conn->c_prot_node = NULL;
				(void)dserver_tcp_send_reset(conn->c_cfd);
				//dserver_conn_cleanup(conn);
				(void)dserver_conn_restore(prot->p_base.p_tcp_main_cpool, conn);
				return;
			} else {
#ifdef DSERVER_NETWORK_NEED_DLOG
				DLOG_DEBUG(LOG_MAIN, "TCP prot[%s:%d] return error.",
					prot->p_name, conn->c_cfd);
#endif
				(void)(*prot->p_ops.p_server_conn_exit)(conn);
				return;
			}
		} /* read size */
	} /* while true */

	/** when protocol decides to continue, or data not arrived */
	tv.tv_sec = prot->p_settings.p_timeout;
	tv.tv_usec = 0;
	event_set(&conn->c_event, conn->c_cfd, EV_READ,
			prot->p_ops.p_server_data_handler, (void *)conn);
	(void)event_base_set(prot->p_main_event, &conn->c_event);
	if (event_add(&conn->c_event, &tv) < 0) {
#ifdef DSERVER_NETWORK_NEED_DLOG
		DLOG_ERR(LOG_MAIN, "prot[%s:%d] reread add event error.",
				prot->p_name, conn->c_cfd);
#endif
		(void)(*prot->p_ops.p_server_conn_exit)(conn);
	}

	return;
}

static int tcp_server_data_exit(int sockfd)
{
	return shutdown(sockfd, SHUT_WR);
}

